/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef PANDA_TOOLING_TEST_JS_SET_VARIABLE_TEST_H
#define PANDA_TOOLING_TEST_JS_SET_VARIABLE_TEST_H

#include "test_util.h"
#include "plugins/ecmascript/runtime/include/tooling/pt_ecmascript_extension.h"
#include "plugins/ecmascript/tests/runtime/tooling/js_test_api.h"
#include "runtime/include/mem/panda_string.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/global_env.h"

namespace ark::tooling::test {
using JSTaggedValue = ark::ecmascript::JSTaggedValue;
using JSThread = ark::ecmascript::JSThread;
class JsSetVariableTest : public ApiTest {
public:
    JsSetVariableTest()
    {
        vmDeath = [this]() {
            ASSERT_TRUE(checked_);
            ASSERT_EQ(breakpointCount_, 4);
            return true;
        };

        loadModule = [this](std::string_view moduleName) {
            if (moduleName.find(pandaFile_.c_str()) == std::string_view::npos) {
                return true;
            }
            // NOLINTNEXTLINE(readability-magic-numbers)
            SetBreakpoint(22);  // setBoolean
            // NOLINTNEXTLINE(readability-magic-numbers)
            SetBreakpoint(26);  // setInt
            // NOLINTNEXTLINE(readability-magic-numbers)
            SetBreakpoint(30);  // setDouble
            // NOLINTNEXTLINE(readability-magic-numbers)
            SetBreakpoint(34);  // setString
            return true;
        };

        // NOLINTBEGIN(readability-magic-numbers)
        breakpoint = [this](PtThread thread, Method *method, PtLocation location) {
            breakpointCount_ += 1;
            auto methodName = method->GetFullName();
            int frameDepth = 0;
            uint32_t currOffset = location.GetBytecodeOffset();
            if (methodName == "_GLOBAL::func_setBoolean_1") {
                VRegValue value = ecmascript::PtEcmaScriptExtension::TaggedValueToVRegValue(JSTaggedValue(true));
                ASSERT_SUCCESS(debugInterface->SetVariable(
                    thread, frameDepth, TestUtil::GetValueRegister(method, "value", currOffset), value));
            } else if (methodName == "_GLOBAL::func_setInt_2") {
                VRegValue value =
                    ecmascript::PtEcmaScriptExtension::TaggedValueToVRegValue(JSTaggedValue(123456789));  // 123456789
                ASSERT_SUCCESS(debugInterface->SetVariable(
                    thread, frameDepth, TestUtil::GetValueRegister(method, "value", currOffset), value));
            } else if (methodName == "_GLOBAL::func_setDouble_3") {
                VRegValue value =
                    ecmascript::PtEcmaScriptExtension::TaggedValueToVRegValue(JSTaggedValue(12345.6789));  // 12345.6789
                ASSERT_SUCCESS(debugInterface->SetVariable(
                    thread, frameDepth, TestUtil::GetValueRegister(method, "value", currOffset), value));
            } else if (methodName == "_GLOBAL::func_setString_4") {
                VRegValue value = ecmascript::JSTestApi::StringToVRegValue("x2348x");
                ASSERT_SUCCESS(debugInterface->SetVariable(
                    thread, frameDepth, TestUtil::GetValueRegister(method, "value", currOffset), value));
            }
            return true;
        };
        // NOLINTEND(readability-magic-numbers)

        methodEntry = [this](PtThread thread, Method *method) {
            auto methodName = method->GetFullName();
            if (methodName == "_GLOBAL::func_checkData_5") {
                checked_ = CheckData(thread, method);
            }
            return true;
        };
    }

    std::pair<const char *, const char *> GetEntryPoint() override
    {
        return {pandaFile_.c_str(), entryPoint_.c_str()};
    }

private:
    static bool GetGlobalVariable(const PandaString &name, VRegValue *out)
    {
        JSThread *jsThread = JSThread::Cast(JSThread::GetCurrent());
        auto ecmaVm = jsThread->GetEcmaVM();
        ark::ecmascript::JSHandle<ark::ecmascript::GlobalEnv> globalEnv = ecmaVm->GetGlobalEnv();
        auto globalObject = globalEnv->GetGlobalObject();

        ark::ecmascript::ObjectFactory *factory = ecmaVm->GetFactory();
        // NOLINTBEGIN(readability-redundant-string-cstr)
        ark::ecmascript::JSHandle<ark::ecmascript::JSTaggedValue> execHandle(factory->NewFromStdString(name.c_str()));
        // NOLINTEND(readability-redundant-string-cstr)
        ark::ecmascript::JSHandle<ark::ecmascript::JSObject> objectHandle(jsThread, globalObject);
        if (!ark::ecmascript::JSObject::HasProperty(jsThread, objectHandle, execHandle)) {
            return false;
        }

        auto property = ark::ecmascript::JSObject::GetProperty(jsThread, objectHandle, execHandle);
        *out = ecmascript::PtEcmaScriptExtension::TaggedValueToVRegValue(property.GetValue().GetTaggedValue());
        return true;
    }

    bool CheckData(PtThread /*unused*/, Method * /*unused*/)
    {
        {
            VRegValue ptBool;
            ASSERT_TRUE(GetGlobalVariable("boolData", &ptBool));
            ASSERT_EQ(ecmascript::PtEcmaScriptExtension::VRegValueToTaggedValue(ptBool).GetRawData(),
                      JSTaggedValue::VALUE_TRUE);
        }
        {
            VRegValue ptInt;
            const int expectedValue = 123456789;
            ASSERT_TRUE(GetGlobalVariable("intData", &ptInt));
            int intValue = ecmascript::PtEcmaScriptExtension::VRegValueToTaggedValue(ptInt).GetInt();
            ASSERT_EQ(intValue, expectedValue);
        }
        {
            VRegValue ptDouble;
            const double expectedValue = 12345.6789;
            ASSERT_TRUE(GetGlobalVariable("doubleData", &ptDouble));
            double doubleValue = ecmascript::PtEcmaScriptExtension::VRegValueToTaggedValue(ptDouble).GetDouble();
            ASSERT_EQ(doubleValue, expectedValue);
        }
        {
            VRegValue ptString;
            PandaString stringValue;
            const PandaString expectedValue = "x2348x";
            ASSERT_TRUE(GetGlobalVariable("stringData", &ptString));
            ASSERT_TRUE(ecmascript::JSTestApi::VRegValueToString(ptString, &stringValue));
            // NOLINTNEXTLINE(readability-string-compare)
            ASSERT_TRUE(stringValue.compare(expectedValue) == 0);
        }
        return true;
    }

    void SetBreakpoint(uint32_t line)
    {
        auto location = TestUtil::GetLocation("SetVariable.js", line, pandaFile_.c_str());
        ASSERT_TRUE(location.GetMethodId().IsValid());
        ASSERT_SUCCESS(debugInterface->SetBreakpoint(location));
    }

    bool checked_ = false;
    uint32_t breakpointCount_ = 0;
    std::string pandaFile_ = "js/SetVariable.abc";
    std::string entryPoint_ = "_GLOBAL::func_main_0";
};

inline std::unique_ptr<ApiTest> GetJsSetVariableTest()
{
    return std::make_unique<JsSetVariableTest>();
}
}  // namespace ark::tooling::test

#endif  // PANDA_TOOLING_TEST_JS_SET_VARIABLE_TEST_H
